<?php

/**
 * @package     Joomla.Administrator
 * @subpackage  com_categories
 *
 * @copyright   Copyright (C) 2005 - 2013 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
jimport('joomla.application.component.controller');
defined('_JEXEC') or die;

/**
 * Categories Component Category Model
 *
 * @package     Joomla.Administrator
 * @subpackage  com_categories
 * @since       1.6
 */
class CategoriesModelCategory extends JModelAdmin {

    /**
     * @var    string  The prefix to use with controller messages.
     * @since  1.6
     */
    protected $text_prefix = 'COM_CATEGORIES';

    /**
     * Method to test whether a record can be deleted.
     *
     * @param   object   $record  A record object.
     *
     * @return  boolean  True if allowed to delete the record. Defaults to the permission set in the component.
     *
     * @since   1.6
     */
    protected function canDelete($record) {
        if (!empty($record->id)) {
            if ($record->published != -2) {
                return;
            }
            $user = JFactory::getUser();

            return $user->authorise('core.delete', $record->extension . '.category.' . (int) $record->id);
        }
    }

    /**
     * Method to test whether a record can have its state changed.
     *
     * @param   object   $record  A record object.
     *
     * @return  boolean  True if allowed to change the state of the record. Defaults to the permission set in the component.
     *
     * @since   1.6
     */
    protected function canEditState($record) {
        $user = JFactory::getUser();

        // Check for existing category.
        if (!empty($record->id)) {
            return $user->authorise('core.edit.state', $record->extension . '.category.' . (int) $record->id);
        }
        // New category, so check against the parent.
        elseif (!empty($record->parent_id)) {
            return $user->authorise('core.edit.state', $record->extension . '.category.' . (int) $record->parent_id);
        }
        // Default to component settings if neither category nor parent known.
        else {
            return $user->authorise('core.edit.state', $record->extension);
        }
    }

    /**
     * Method to get a table object, load it if necessary.
     *
     * @param   string  $type    The table name. Optional.
     * @param   string  $prefix  The class prefix. Optional.
     * @param   array   $config  Configuration array for model. Optional.
     *
     * @return  JTable  A JTable object
     *
     * @since   1.6
     */
    public function getTable($type = 'Category', $prefix = 'CategoriesTable', $config = array()) {
        return JTable::getInstance($type, $prefix, $config);
    }

    /**
     * Auto-populate the model state.
     *
     * Note. Calling getState in this method will result in recursion.
     *
     * @return  void
     *
     * @since   1.6
     */
    protected function populateState() {
        $app = JFactory::getApplication('administrator');

        $parentId = $app->input->getInt('parent_id');
        $this->setState('category.parent_id', $parentId);

        // Load the User state.
        $pk = $app->input->getInt('id');
        $this->setState($this->getName() . '.id', $pk);

        $extension = $app->input->get('extension', 'com_content');
        $this->setState('category.extension', $extension);
        $parts = explode('.', $extension);

        // Extract the component name
        $this->setState('category.component', $parts[0]);

        // Extract the optional section name
        $this->setState('category.section', (count($parts) > 1) ? $parts[1] : null);

        // Load the parameters.
        $params = JComponentHelper::getParams('com_categories');
        $this->setState('params', $params);
    }

    /**
     * Method to get a category.
     *
     * @param   integer  $pk  An optional id of the object to get, otherwise the id from the model state is used.
     *
     * @return  mixed    Category data object on success, false on failure.
     *
     * @since   1.6
     */
    public function getItem($pk = null) {
        if ($result = parent::getItem($pk)) {

            // Prime required properties.
            if (empty($result->id)) {
                $result->parent_id = $this->getState('category.parent_id');
                $result->extension = $this->getState('category.extension');
            }

            // Convert the metadata field to an array.
            $registry = new JRegistry;
            $registry->loadString($result->metadata);
            $result->metadata = $registry->toArray();

            // Convert the created and modified dates to local user time for display in the form.
            $tz = new DateTimeZone(JFactory::getApplication()->getCfg('offset'));

            if ((int) $result->created_time) {
                $date = new JDate($result->created_time);
                $date->setTimezone($tz);
                $result->created_time = $date->toSql(true);
            } else {
                $result->created_time = null;
            }

            if ((int) $result->modified_time) {
                $date = new JDate($result->modified_time);
                $date->setTimezone($tz);
                $result->modified_time = $date->toSql(true);
            } else {
                $result->modified_time = null;
            }

            if (!empty($result->id)) {
                $result->tags = new JHelperTags;
                $result->tags->getTagIds($result->id, $result->extension . '.category');
            }
        }

        $assoc = $this->getAssoc();
        if ($assoc) {
            if ($result->id != null) {
                $result->associations = CategoriesHelper::getAssociations($result->id, $result->extension);
                JArrayHelper::toInteger($result->associations);
            } else {
                $result->associations = array();
            }
        }

        return $result;
    }

    /**
     * Method to get the row form.
     *
     * @param   array    $data      Data for the form.
     * @param   boolean  $loadData  True if the form is to load its own data (default case), false if not.
     *
     * @return  mixed    A JForm object on success, false on failure
     *
     * @since   1.6
     */
    public function getForm($data = array(), $loadData = true) {
        $extension = $this->getState('category.extension');
        $jinput = JFactory::getApplication()->input;

        // A workaround to get the extension into the model for save requests.
        if (empty($extension) && isset($data['extension'])) {
            $extension = $data['extension'];
            $parts = explode('.', $extension);

            $this->setState('category.extension', $extension);
            $this->setState('category.component', $parts[0]);
            $this->setState('category.section', @$parts[1]);
        }

        // Get the form.
        $form = $this->loadForm('com_categories.category' . $extension, 'category', array('control' => 'jform', 'load_data' => $loadData));
        if (empty($form)) {
            return false;
        }

        // Modify the form based on Edit State access controls.
        if (empty($data['extension'])) {
            $data['extension'] = $extension;
        }
        $user = JFactory::getUser();
        if (!$user->authorise('core.edit.state', $extension . '.category.' . $jinput->get('id'))) {
            // Disable fields for display.
            $form->setFieldAttribute('ordering', 'disabled', 'true');
            $form->setFieldAttribute('published', 'disabled', 'true');

            // Disable fields while saving.
            // The controller has already verified this is a record you can edit.
            $form->setFieldAttribute('ordering', 'filter', 'unset');
            $form->setFieldAttribute('published', 'filter', 'unset');
        }

        return $form;
    }

    /**
     * A protected method to get the where clause for the reorder
     * This ensures that the row will be moved relative to a row with the same extension
     *
     * @param   JCategoryTable  $table  Current table instance
     *
     * @return  array           An array of conditions to add to add to ordering queries.
     *
     * @since   1.6
     */
    protected function getReorderConditions($table) {
        return 'extension = ' . $this->_db->quote($table->extension);
    }

    /**
     * Method to get the data that should be injected in the form.
     *
     * @return  mixed  The data for the form.
     *
     * @since   1.6
     */
    protected function loadFormData() {
        // Check the session for previously entered form data.
        $data = JFactory::getApplication()->getUserState('com_categories.edit.' . $this->getName() . '.data', array());

        if (empty($data)) {
            $data = $this->getItem();
        }

        $this->preprocessData('com_categories.category', $data);

        return $data;
    }

    /**
     * Method to preprocess the form.
     *
     * @param   JForm   $form   A JForm object.
     * @param   mixed   $data   The data expected for the form.
     * @param   string  $group  The name of the plugin group to import.
     *
     * @return  void
     *
     * @see     JFormField
     * @since   1.6
     * @throws  Exception if there is an error in the form event.
     */
    protected function preprocessForm(JForm $form, $data, $group = 'content') {
        jimport('joomla.filesystem.path');

        $lang = JFactory::getLanguage();
        $component = $this->getState('category.component');
        $section = $this->getState('category.section');
        $extension = JFactory::getApplication()->input->get('extension', null);

        // Get the component form if it exists
        $name = 'category' . ($section ? ('.' . $section) : '');

        // Looking first in the component models/forms folder
        $path = JPath::clean(JPATH_ADMINISTRATOR . "/components/$component/models/forms/$name.xml");

        // Old way: looking in the component folder
        if (!file_exists($path)) {
            $path = JPath::clean(JPATH_ADMINISTRATOR . "/components/$component/$name.xml");
        }

        if (file_exists($path)) {
            $lang->load($component, JPATH_BASE, null, false, false);
            $lang->load($component, JPATH_BASE, $lang->getDefault(), false, false);
            $lang->load($component, JPATH_BASE . '/components/' . $component, null, false, false);
            $lang->load($component, JPATH_BASE . '/components/' . $component, $lang->getDefault(), false, false);

            if (!$form->loadFile($path, false)) {
                throw new Exception(JText::_('JERROR_LOADFILE_FAILED'));
            }
        }

        // Try to find the component helper.
        $eName = str_replace('com_', '', $component);
        $path = JPath::clean(JPATH_ADMINISTRATOR . "/components/$component/helpers/category.php");

        if (file_exists($path)) {
            require_once $path;
            $cName = ucfirst($eName) . ucfirst($section) . 'HelperCategory';

            if (class_exists($cName) && is_callable(array($cName, 'onPrepareForm'))) {
                $lang->load($component, JPATH_BASE, null, false, false) || $lang->load($component, JPATH_BASE . '/components/' . $component, null, false, false) || $lang->load($component, JPATH_BASE, $lang->getDefault(), false, false) || $lang->load($component, JPATH_BASE . '/components/' . $component, $lang->getDefault(), false, false);
                call_user_func_array(array($cName, 'onPrepareForm'), array(&$form));

                // Check for an error.
                if ($form instanceof Exception) {
                    $this->setError($form->getMessage());
                    return false;
                }
            }
        }

        // Set the access control rules field component value.
        $form->setFieldAttribute('rules', 'component', $component);
        $form->setFieldAttribute('rules', 'section', $name);

        // Association category items
        $assoc = $this->getAssoc();
        if ($assoc) {
            $languages = JLanguageHelper::getLanguages('lang_code');

            // Force to array (perhaps move to $this->loadFormData())
            $data = (array) $data;

            $addform = new SimpleXMLElement('<form />');
            $fields = $addform->addChild('fields');
            $fields->addAttribute('name', 'associations');
            $fieldset = $fields->addChild('fieldset');
            $fieldset->addAttribute('name', 'item_associations');
            $fieldset->addAttribute('description', 'COM_CATEGORIES_ITEM_ASSOCIATIONS_FIELDSET_DESC');
            $add = false;
            foreach ($languages as $tag => $language) {
                if (empty($data['language']) || $tag != $data['language']) {
                    $add = true;
                    $field = $fieldset->addChild('field');
                    $field->addAttribute('name', $tag);
                    $field->addAttribute('type', 'modal_category');
                    $field->addAttribute('language', $tag);
                    $field->addAttribute('label', $language->title);
                    $field->addAttribute('translate_label', 'false');
                    $field->addAttribute('extension', $extension);
                    $field->addAttribute('edit', 'true');
                    $field->addAttribute('clear', 'true');
                }
            }
            if ($add) {
                $form->load($addform, false);
            }
        }

        // Trigger the default form events.
        parent::preprocessForm($form, $data, $group);
    }

    /**
     * Method to save the form data.
     *
     * @param   array    $data  The form data.
     *
     * @return  boolean  True on success.
     *
     * @since   1.6
     */
    public function save($data) {
        $dispatcher = JEventDispatcher::getInstance();
        $table = $this->getTable();
        $input = JFactory::getApplication()->input;
        $pk = (!empty($data['id'])) ? $data['id'] : (int) $this->getState($this->getName() . '.id');
        $isNew = true;

        if ((!empty($data['tags']) && $data['tags'][0] != '')) {
            $table->newTags = $data['tags'];
        }

        // Include the content plugins for the on save events.
        JPluginHelper::importPlugin('content');

        // Load the row if saving an existing category.
        if ($pk > 0) {
            $table->load($pk);
            $isNew = false;
        }

        // Set the new parent id if parent id not matched OR while New/Save as Copy .
        if ($table->parent_id != $data['parent_id'] || $data['id'] == 0) {
            $table->setLocation($data['parent_id'], 'last-child');
        }

        // Alter the title for save as copy
        if ($input->get('task') == 'save2copy') {
            list($title, $alias) = $this->generateNewTitle($data['parent_id'], $data['alias'], $data['title']);
            $data['title'] = $title;
            $data['alias'] = $alias;
            $data['published'] = 0;
        }

        // Bind the data.
        if (!$table->bind($data)) {
            $this->setError($table->getError());
            return false;
        }

        // Bind the rules.
        if (isset($data['rules'])) {
            $rules = new JAccessRules($data['rules']);
            $table->setRules($rules);
        }

        // Check the data.
        if (!$table->check()) {
            $this->setError($table->getError());
            return false;
        }

        // Trigger the onContentBeforeSave event.
        $result = $dispatcher->trigger($this->event_before_save, array($this->option . '.' . $this->name, &$table, $isNew));
        if (in_array(false, $result, true)) {
            $this->setError($table->getError());
            return false;
        }

        // Store the data.
        if (!$table->store()) {
            $this->setError($table->getError());
            return false;
        }

        $assoc = $this->getAssoc();
        if ($assoc) {

            // Adding self to the association
            $associations = $data['associations'];

            foreach ($associations as $tag => $id) {
                if (empty($id)) {
                    unset($associations[$tag]);
                }
            }

            // Detecting all item menus
            $all_language = $table->language == '*';

            if ($all_language && !empty($associations)) {
                JError::raiseNotice(403, JText::_('COM_CATEGORIES_ERROR_ALL_LANGUAGE_ASSOCIATED'));
            }

            $associations[$table->language] = $table->id;

            // Deleting old association for these items
            $db = JFactory::getDbo();
            $query = $db->getQuery(true)
                    ->delete('#__associations')
                    ->where($db->quoteName('context') . ' = ' . $db->quote('com_categories.item'))
                    ->where($db->quoteName('id') . ' IN (' . implode(',', $associations) . ')');
            $db->setQuery($query);
            $db->execute();

            if ($error = $db->getErrorMsg()) {
                $this->setError($error);
                return false;
            }

            if (!$all_language && count($associations)) {
                // Adding new association for these items
                $key = md5(json_encode($associations));
                $query->clear()
                        ->insert('#__associations');

                foreach ($associations as $id) {
                    $query->values($id . ',' . $db->quote('com_categories.item') . ',' . $db->quote($key));
                }

                $db->setQuery($query);
                $db->execute();

                if ($error = $db->getErrorMsg()) {
                    $this->setError($error);
                    return false;
                }
            }
        }

        // Trigger the onContentAfterSave event.
        $dispatcher->trigger($this->event_after_save, array($this->option . '.' . $this->name, &$table, $isNew));

        // Rebuild the path for the category:
        if (!$table->rebuildPath($table->id)) {
            $this->setError($table->getError());
            return false;
        }

        // Rebuild the paths of the category's children:
        if (!$table->rebuild($table->id, $table->lft, $table->level, $table->path)) {
            $this->setError($table->getError());
            return false;
        }

        $this->setState($this->getName() . '.id', $table->id);

        // Clear the cache
        $this->cleanCache();

        return true;
    }

    function setMessage($text) {
        $previous = $this->_message;
        $this->_message = $text;
        return $previous;
    }

    /**
     * Method to change the published state of one or more records.
     *
     * @param   array    &$pks   A list of the primary keys to change.
     * @param   integer  $value  The value of the published state.
     *
     * @return  boolean  True on success.
     *
     * @since   2.5
     */
    public function publish(&$pks, $value = 1) {
        $task = JRequest::getVar('task', array(), 'array');

        if ($task == 'trash') {
            $check_id = $pks[0];
            switch ($check_id) {

                case '8':
                case '9':
                case '10':
                case '11':
                case '13':
                case '19':
                    JError::raiseNotice(500, JText::_('This categories could not be delete because it for homepage. Please choose another one!'));
                    JRequest::setVar('notrash', 'yes');
                    return false;
                    break;
                default:
                    break;
            }
        }
        if (parent::publish($pks, $value)) {
            $dispatcher = JEventDispatcher::getInstance();
            $extension = JFactory::getApplication()->input->get('extension');

            // Include the content plugins for the change of category state event.
            JPluginHelper::importPlugin('content');

            // Trigger the onCategoryChangeState event.
            $dispatcher->trigger('onCategoryChangeState', array($extension, $pks, $value));

            return true;
        }
    }

    /**
     * Method rebuild the entire nested set tree.
     *
     * @return  boolean  False on failure or error, true otherwise.
     *
     * @since   1.6
     */
    public function rebuild() {
        // Get an instance of the table object.
        $table = $this->getTable();

        if (!$table->rebuild()) {
            $this->setError($table->getError());
            return false;
        }

        // Clear the cache
        $this->cleanCache();

        return true;
    }

    /**
     * Method to save the reordered nested set tree.
     * First we save the new order values in the lft values of the changed ids.
     * Then we invoke the table rebuild to implement the new ordering.
     *
     * @param   array    $idArray    An array of primary key ids.
     * @param   integer  $lft_array  The lft value
     *
     * @return  boolean  False on failure or error, True otherwise
     *
     * @since   1.6
     */
    public function saveorder($idArray = null, $lft_array = null) {
        // Get an instance of the table object.
        $table = $this->getTable();

        if (!$table->saveorder($idArray, $lft_array)) {
            $this->setError($table->getError());
            return false;
        }

        // Clear the cache
        $this->cleanCache();

        return true;
    }

    protected function batchTag($value, $pks, $contexts) {
        // Set the variables
        $user = JFactory::getUser();
        $table = $this->getTable();

        foreach ($pks as $pk) {
            if ($user->authorise('core.edit', $contexts[$pk])) {
                $table->reset();
                $table->load($pk);
                $tags = array($value);

                /**
                 * @var  JTableObserverTags  $tagsObserver
                 */
                $tagsObserver = $table->getObserverOfClass('JTableObserverTags');
                $result = $tagsObserver->setNewTags($tags, false);

                if (!$result) {
                    $this->setError($table->getError());

                    return false;
                }
            } else {
                $this->setError(JText::_('JLIB_APPLICATION_ERROR_BATCH_CANNOT_EDIT'));

                return false;
            }
        }

        // Clean the cache
        $this->cleanCache();

        return true;
    }

    /**
     * Batch copy categories to a new category.
     *
     * @param   integer  $value     The new category.
     * @param   array    $pks       An array of row IDs.
     * @param   array    $contexts  An array of item contexts.
     *
     * @return  mixed    An array of new IDs on success, boolean false on failure.
     *
     * @since   1.6
     */
    protected function batchCopy($value, $pks, $contexts) {
        // $value comes as {parent_id}.{extension}
        $parts = explode('.', $value);
        $parentId = (int) JArrayHelper::getValue($parts, 0, 1);

        $table = $this->getTable();
        $db = $this->getDbo();
        $user = JFactory::getUser();
        $extension = JFactory::getApplication()->input->get('extension', '', 'word');
        $i = 0;

        // Check that the parent exists
        if ($parentId) {
            if (!$table->load($parentId)) {
                if ($error = $table->getError()) {
                    // Fatal error
                    $this->setError($error);
                    return false;
                } else {
                    // Non-fatal error
                    $this->setError(JText::_('JGLOBAL_BATCH_MOVE_PARENT_NOT_FOUND'));
                    $parentId = 0;
                }
            }
            // Check that user has create permission for parent category
            $canCreate = ($parentId == $table->getRootId()) ? $user->authorise('core.create', $extension) : $user->authorise('core.create', $extension . '.category.' . $parentId);
            if (!$canCreate) {
                // Error since user cannot create in parent category
                $this->setError(JText::_('COM_CATEGORIES_BATCH_CANNOT_CREATE'));
                return false;
            }
        }

        // If the parent is 0, set it to the ID of the root item in the tree
        if (empty($parentId)) {
            if (!$parentId = $table->getRootId()) {
                $this->setError($db->getErrorMsg());
                return false;
            }
            // Make sure we can create in root
            elseif (!$user->authorise('core.create', $extension)) {
                $this->setError(JText::_('COM_CATEGORIES_BATCH_CANNOT_CREATE'));
                return false;
            }
        }

        // We need to log the parent ID
        $parents = array();

        // Calculate the emergency stop count as a precaution against a runaway loop bug
        $query = $db->getQuery(true)
                ->select('COUNT(id)')
                ->from($db->quoteName('#__categories'));
        $db->setQuery($query);

        try {
            $count = $db->loadResult();
        } catch (RuntimeException $e) {
            $this->setError($e->getMessage());
            return false;
        }

        // Parent exists so we let's proceed
        while (!empty($pks) && $count > 0) {
            // Pop the first id off the stack
            $pk = array_shift($pks);

            $table->reset();

            // Check that the row actually exists
            if (!$table->load($pk)) {
                if ($error = $table->getError()) {
                    // Fatal error
                    $this->setError($error);
                    return false;
                } else {
                    // Not fatal error
                    $this->setError(JText::sprintf('JGLOBAL_BATCH_MOVE_ROW_NOT_FOUND', $pk));
                    continue;
                }
            }

            // Copy is a bit tricky, because we also need to copy the children
            $query->clear()
                    ->select('id')
                    ->from($db->quoteName('#__categories'))
                    ->where('lft > ' . (int) $table->lft)
                    ->where('rgt < ' . (int) $table->rgt);
            $db->setQuery($query);
            $childIds = $db->loadColumn();

            // Add child ID's to the array only if they aren't already there.
            foreach ($childIds as $childId) {
                if (!in_array($childId, $pks)) {
                    array_push($pks, $childId);
                }
            }

            // Make a copy of the old ID and Parent ID
            $oldId = $table->id;
            $oldParentId = $table->parent_id;

            // Reset the id because we are making a copy.
            $table->id = 0;

            // If we a copying children, the Old ID will turn up in the parents list
            // otherwise it's a new top level item
            $table->parent_id = isset($parents[$oldParentId]) ? $parents[$oldParentId] : $parentId;

            // Set the new location in the tree for the node.
            $table->setLocation($table->parent_id, 'last-child');

            // TODO: Deal with ordering?
            // $table->ordering	= 1;
            $table->level = null;
            $table->asset_id = null;
            $table->lft = null;
            $table->rgt = null;

            // Alter the title & alias
            list($title, $alias) = $this->generateNewTitle($table->parent_id, $table->alias, $table->title);
            $table->title = $title;
            $table->alias = $alias;

            // Store the row.
            if (!$table->store()) {
                $this->setError($table->getError());
                return false;
            }

            // Get the new item ID
            $newId = $table->get('id');

            // Add the new ID to the array
            $newIds[$i] = $newId;
            $i++;

            // Now we log the old 'parent' to the new 'parent'
            $parents[$oldId] = $table->id;
            $count--;
        }

        // Rebuild the hierarchy.
        if (!$table->rebuild()) {
            $this->setError($table->getError());
            return false;
        }

        // Rebuild the tree path.
        if (!$table->rebuildPath($table->id)) {
            $this->setError($table->getError());
            return false;
        }

        return $newIds;
    }

    /**
     * Batch move categories to a new category.
     *
     * @param   integer  $value     The new category ID.
     * @param   array    $pks       An array of row IDs.
     * @param   array    $contexts  An array of item contexts.
     *
     * @return  boolean  True on success.
     *
     * @since   1.6
     */
    protected function batchMove($value, $pks, $contexts) {
        $parentId = (int) $value;

        $table = $this->getTable();
        $db = $this->getDbo();
        $query = $db->getQuery(true);
        $user = JFactory::getUser();
        $extension = JFactory::getApplication()->input->get('extension', '', 'word');

        // Check that the parent exists.
        if ($parentId) {
            if (!$table->load($parentId)) {
                if ($error = $table->getError()) {
                    // Fatal error
                    $this->setError($error);

                    return false;
                } else {
                    // Non-fatal error
                    $this->setError(JText::_('JGLOBAL_BATCH_MOVE_PARENT_NOT_FOUND'));
                    $parentId = 0;
                }
            }
            // Check that user has create permission for parent category
            $canCreate = ($parentId == $table->getRootId()) ? $user->authorise('core.create', $extension) : $user->authorise('core.create', $extension . '.category.' . $parentId);
            if (!$canCreate) {
                // Error since user cannot create in parent category
                $this->setError(JText::_('COM_CATEGORIES_BATCH_CANNOT_CREATE'));
                return false;
            }

            // Check that user has edit permission for every category being moved
            // Note that the entire batch operation fails if any category lacks edit permission
            foreach ($pks as $pk) {
                if (!$user->authorise('core.edit', $extension . '.category.' . $pk)) {
                    // Error since user cannot edit this category
                    $this->setError(JText::_('COM_CATEGORIES_BATCH_CANNOT_EDIT'));
                    return false;
                }
            }
        }

        // We are going to store all the children and just move the category
        $children = array();

        // Parent exists so we let's proceed
        foreach ($pks as $pk) {
            // Check that the row actually exists
            if (!$table->load($pk)) {
                if ($error = $table->getError()) {
                    // Fatal error
                    $this->setError($error);
                    return false;
                } else {
                    // Not fatal error
                    $this->setError(JText::sprintf('JGLOBAL_BATCH_MOVE_ROW_NOT_FOUND', $pk));
                    continue;
                }
            }

            // Set the new location in the tree for the node.
            $table->setLocation($parentId, 'last-child');

            // Check if we are moving to a different parent
            if ($parentId != $table->parent_id) {
                // Add the child node ids to the children array.
                $query->clear()
                        ->select('id')
                        ->from($db->quoteName('#__categories'))
                        ->where($db->quoteName('lft') . ' BETWEEN ' . (int) $table->lft . ' AND ' . (int) $table->rgt);
                $db->setQuery($query);

                try {
                    $children = array_merge($children, (array) $db->loadColumn());
                } catch (RuntimeException $e) {
                    $this->setError($e->getMessage());
                    return false;
                }
            }

            // Store the row.
            if (!$table->store()) {
                $this->setError($table->getError());
                return false;
            }

            // Rebuild the tree path.
            if (!$table->rebuildPath()) {
                $this->setError($table->getError());
                return false;
            }
        }

        // Process the child rows
        if (!empty($children)) {
            // Remove any duplicates and sanitize ids.
            $children = array_unique($children);
            JArrayHelper::toInteger($children);
        }

        return true;
    }

    /**
     * Custom clean the cache of com_content and content modules
     *
     * @since   1.6
     */
    protected function cleanCache($group = null, $client_id = 0) {
        $extension = JFactory::getApplication()->input->get('extension');
        switch ($extension) {
            case 'com_content':
                parent::cleanCache('com_content');
                parent::cleanCache('mod_articles_archive');
                parent::cleanCache('mod_articles_categories');
                parent::cleanCache('mod_articles_category');
                parent::cleanCache('mod_articles_latest');
                parent::cleanCache('mod_articles_news');
                parent::cleanCache('mod_articles_popular');
                break;
            default:
                parent::cleanCache($extension);
                break;
        }
    }

    /**
     * Method to change the title & alias.
     *
     * @param   integer  $parent_id  The id of the parent.
     * @param   string   $alias      The alias.
     * @param   string   $title      The title.
     *
     * @return  array    Contains the modified title and alias.
     *
     * @since   1.7
     */
    protected function generateNewTitle($parent_id, $alias, $title) {
        // Alter the title & alias
        $table = $this->getTable();
        while ($table->load(array('alias' => $alias, 'parent_id' => $parent_id))) {
            $title = JString::increment($title);
            $alias = JString::increment($alias, 'dash');
        }

        return array($title, $alias);
    }

    public function getAssoc() {
        static $assoc = null;

        if (!is_null($assoc)) {
            return $assoc;
        }

        $app = JFactory::getApplication();
        $extension = $this->getState('category.extension');

        $assoc = isset($app->item_associations) ? $app->item_associations : 0;
        $extension = explode('.', $extension);
        $component = array_shift($extension);
        $cname = str_replace('com_', '', $component);

        if (!$assoc || !$component || !$cname) {
            $assoc = false;
        } else {
            $hname = $cname . 'HelperAssociation';
            JLoader::register($hname, JPATH_SITE . '/components/' . $component . '/helpers/association.php');

            $assoc = class_exists($hname) && !empty($hname::$category_association);
        }

        return $assoc;
    }

}
